﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Storage
{
    using Exceptions;
    using EntityModels;

    #region StorageManager
   
    /// <summary>
    /// Rapresents the way in which the modules can handle and persist their own data.
    /// </summary>
    public sealed partial class StorageManager
    {
        #region GetTripById

        /// <summary>
        /// Get a trip from the storage.
        /// </summary>
        /// <param name="tripId">The Trip's Id.</param>
        /// <returns>The trip associated that has tripId.</returns>
        public Trip GetTripById(int tripId)
        {
            var result = from trip in Entities.Trips
                         where trip.Id == tripId
                         select trip;
            try
            {
                Trip t = result.First();

                List<Stage> stagesList = t.Stages.OrderBy(o => o.Ordinal).ToList();
                t.Stages.Clear();

                foreach (var s in stagesList)
                    t.Stages.Add(s);

                return t;
            }
            catch (InvalidOperationException)
            {
                throw new TripNotFoundException(tripId);
            }
        }

        #endregion

        #region GetPlaceById

        /// <summary>
        /// Get a place from the storage.
        /// </summary>
        /// <param name="placeId">The Place's Id.</param>
        /// <returns>The place associated that has placeId.</returns>
        public Place GetPlaceById(int placeId)
        {
            try
            {
                return Entities.Places.First(p => p.Id == placeId);
            }
            catch (InvalidOperationException)
            {
                throw new PlaceNotFoundException(placeId);
            }
        }

        #endregion

        #region GetPlaceByLatLong

        /// <summary>
        /// Returns the place at the specified latitude and longitude.
        /// </summary>
        /// <returns>A Place if the place exists or null otherwise.</returns>
        public Place GetPlaceByLatLong(double latitude, double longitude)
        {
            return StorageManager.Instance.Entities.Place_exists(latitude, longitude).FirstOrDefault();
        }

        #endregion

        #region GetDraftTripsByUserId

        /// <summary>
        /// Returns the id's of all draft-trip of a user.
        /// </summary>
        /// <returns>A List of int.</returns>
        public List<int> GetDraftTripsByUserId(int userId)
        {
            // NB: This line is only used to check if the user exists.
            User user = GetUserById(userId);

            var drafts = from trip in Entities.Trips
                         where trip.Draft == true && trip.OwnerId == userId
                         select trip.Id;

            return drafts.ToList();
        }

        #endregion

        #region GetTripsWithReservationRequestByUserId

        /// <summary>
        /// Returns all trips with a request of a seat reservation for a certain user.
        /// </summary>
        /// <param name="userId">The user's Id.</param>
        /// <returns>A List of Trip.</returns>
        public List<Trip> GetTripsWithReservationRequestByUserId(int userId)
        {
            // NB: This line is only used to check if the user exists.
            User user = GetUserById(userId);

            var trips = from request in Entities.ReservationRequests
                        where request.User == user
                        select request.Trip;

            // FIX: "Distinct" could be unnecessary.
            return trips.Distinct().ToList();
        }

        #endregion

        #region GetReservedTripsByUserId

        /// <summary>
        /// Returns the id's of all trips with a confirmation for a seat reservation
        /// for a certain user.
        /// </summary>
        /// <returns>A List of int.</returns>
        public List<int> GetReservedTripsByUserId(int userId)
        {
            // NB: This line is only used to check if the user exists.
            User user = GetUserById(userId);

            var trips = from stage in Entities.Users_Stages
                        where stage.User == user
                        select stage.Stage.TripId;

            return trips.Distinct().ToList();
        }

        #endregion

        #region GetTripListByOwnerId

        /// <summary>
        /// Returns the id's of all trips of a certain owner (an user).
        /// </summary>
        ///<returns>A List of int.</returns>
        public List<int> GetTripListByOwnerId(int userId)
        {
            // NB: This line is only used to check if the user exists.
            User user = GetUserById(userId);

            var trips = from trip in Entities.Trips
                        where trip.OwnerId == userId
                        select trip.Id;

            return trips.ToList();
        }

        #endregion

        #region GetUserListByStagePk

        /// <summary>
        /// Returns the id's of all users in a certain stage.
        /// </summary>
        /// <returns>A List of int.</returns>
        public List<int> GetUserListByStagePk(int stageId)
        {
            if (!Entities.Stages.Any(s => s.Id == stageId))
                throw new StageNotFoundException(stageId);

            var users = from user_stage in Entities.Users_Stages
                        where user_stage.StageId == stageId
                        select user_stage.UserId;

            return users.ToList();
        }

        #endregion    

        #region AddReservationRequest

        /// <summary>
        /// Adds a reservation request for the specified user to the specified trip.
        /// </summary>
        /// <param name="tripId">The tripId</param>
        /// <param name="userName">The Username</param>
        /// <param name="requestedSeats">The number of seats requested</param>
        /// <param name="origin">The origin</param>
        /// <param name="destination">The destination</param>
        /// <param name="startTolerance"></param>
        /// <param name="endTolerance"></param>
        /// <param name="notes">The notes</param>
        /// <param name="status">The status</param>
        /// <returns>The id of the newly created reservation request</returns>
        public int AddReservationRequest(int tripId, string userName, byte requestedSeats, Place origin,
            Place destination, float startTolerance, float endTolerance, string notes, byte status)
        {
            ReservationRequest rr = new ReservationRequest
            {
                TripId = tripId,
                User = GetUserByUserName(userName),
                RequestedSeats = requestedSeats,
                OriginPlace = origin,
                DestinationPlace = destination,
                StartTolerance = startTolerance,
                EndTolerance = endTolerance,
                Notes = notes,
                Date = DateTime.Now,
                Status = status
            };

            GetTripById(tripId).ReservationRequests.Add(rr);

            Entities.SubmitChanges();

            return rr.Id;
        }

        /// <summary>
        /// Adds a reservation request for the specified user to the specified trip
        /// </summary>
        /// <param name="tripId">The tripId</param>
        /// <param name="userName">The Username</param>
        /// <param name="requestedSeats">The number of seats requested</param>
        /// <param name="origin">The origin</param>
        /// <param name="destination">The destination</param>
        /// <param name="notes">The notes</param>
        /// <param name="status">The status</param>
        /// <returns>The id of the newly created reservation request</returns>
        public int AddReservationRequest(int tripId, string userName, byte requestedSeats, Place origin,
            Place destination, string notes, byte status)
        {
            return AddReservationRequest(tripId, userName, requestedSeats, origin, destination, 0, 0, notes, status);
        }

        /// <summary>
        /// Adds a reservation request for the specified user to the specified trip
        /// </summary>
        /// <param name="tripId">The tripId</param>
        /// <param name="userName">The Username</param>
        /// <param name="requestedSeats">The number of seats requested</param>
        /// <param name="origin">The origin</param>
        /// <param name="destination">The destination</param>
        /// <param name="startTolerance"></param>
        /// <param name="endTolerance"></param>
        /// <param name="status">The status</param>
        /// <returns>The id of the newly created reservation request</returns>
        public int AddReservationRequest(int tripId, string userName, byte requestedSeats, Place origin,
            Place destination, float startTolerance, float endTolerance, byte status)
        {
            return AddReservationRequest(tripId, userName, requestedSeats, origin, destination, startTolerance,
                endTolerance, null, status);
        }

        #endregion

        #region SetFrequecyDetails

        /// <summary>
        /// Sets the entry to handle the frequency of a Trip.
        /// </summary>
        /// <param name="trip">The trip.</param>
        /// <param name="frequencyFrom">DateTime: it marks the beginning of the frequency period.</param>
        /// <param name="frequencyTo">DateTime: it marks the end of the frequency period.</param>
        /// <param name="departureDayInfos">
        /// An Array of 7 position. It contains data for the new DepartureDate fields.
        /// departureDayInfo[0] contains frequency's infos for each Sunday beetween frequencyFrom and frequencyTo.
        /// departureDayInfo[1] contains frequency's infos for each Monday beetween frequencyFrom and frequencyTo.
        /// ...
        /// departureDayInfo[6] contains frequency's infos for each Saturday beetween frequencyFrom and frequencyTo.
        /// 
        /// If departureDayInfo[i].value == "DayNotScheduled", it means that Trips won't be repeated in day "i" of the week.
        /// departureDayInfo[i].value must have the format "hh:mm:ss" or "hh,mm,ss" or "hh.mm.ss", etc..
        /// </param>
        public void SetFrequencyDetails(Trip trip, DateTime frequencyFrom, DateTime frequencyTo, string[] departureDayInfos)
        { 
            FrequencyDetails frequency;

            if (trip.FrequencyDetails != null)
            {
                trip.FrequencyDetails.From = frequencyFrom;
                trip.FrequencyDetails.To = frequencyTo;
                frequency = trip.FrequencyDetails;

                var f_df = from fdf in Entities.Frequency_DailyFrequencies
                           where fdf.FrequencyId == trip.FrequencyDetails.Id
                           select fdf;

                var daysIDs = f_df.Select(f => f.DailyId);

                Entities.Frequency_DailyFrequencies.DeleteAllOnSubmit(f_df);

                foreach (var id in daysIDs)
                {
                    DailyFrequencyDetails d = (from dfd in Entities.DailyFrequencyDetails
                                              where dfd.Id == id
                                              select dfd).First();

                    Entities.DailyFrequencyDetails.DeleteOnSubmit(d);
                }
            }
            else
            {
                frequency = new FrequencyDetails
                {
                    From = frequencyFrom,
                    To = frequencyTo,
                    Trip = trip
                };
            }

            for (byte key = 0; key < departureDayInfos.Length; key++)
            {
                DailyFrequencyDetails daily;

                if (departureDayInfos[key] != "DayNotScheduled")
                {
                    daily = new DailyFrequencyDetails()
                    {
                        DayOfWeek = key,
                        FromHours = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day,
                                                 int.Parse((departureDayInfos[key]).Substring(0, 2)),
                                                 int.Parse((departureDayInfos[key]).Substring(3, 2)), 0),
                        IsValid = true,
                    };
                }
                else
                {
                    daily = new DailyFrequencyDetails()
                    {
                        DayOfWeek = key,
                        FromHours = DateTime.Now,
                        IsValid = false,

                    };
                }

                Entities.DailyFrequencyDetails.InsertOnSubmit(daily);

                Frequency_DailyFrequency f_df = new Frequency_DailyFrequency()
                {
                    DailyFrequencyDetails = daily,
                    FrequencyDetails = frequency
                };

                Entities.Frequency_DailyFrequencies.InsertOnSubmit(f_df);
            }

            StorageManager.Instance.Entities.SubmitChanges();
        }
            
        #endregion
    }

    #endregion
}
